package org.lisy.distributed.id;

import java.util.UUID;

public class GenerateId {

	/**
	 * ID 生成系统应该做到如下几点：
	 * 平均延迟和 TP999 延迟都要尽可能低；
	 * 可用性 5 个 9
	 * 高 QPS
	 */
	
	/**
	 * 生成分布式锁的几种方式：
	 * UUID: 
	 *  - 优点: 生成简单，本地生成无网络消耗，具有唯一性
	 *  - 缺点: 长度较长不易于存储，不适合作为 DB 的主键，MySQL 官方有明确的建议主键要尽量越短越好；信息不安全，基于 MAC 地址生成 UUID 的算法可能会造成 MAC 地址泄露 
	 * 数据库自增ID 
	 *  - 优点: 实现简单，ID单调自增，数值类型查询速度快
	 *  - 缺点: DB单点存在宕机风险，高并发场景成为性能瓶颈
	 * 数据库多主模式 
	 *  - 优点: 解决 DB 单点问题
	 *  - 缺点: 不利于后续扩容，而且实际上单个数据库自身压力还是大，依旧无法满足高并发场景
	 * 号段模式 - 采用版本号 version 乐观锁方式批次获取
	 *  - 优点: 不强依赖于数据库，不会频繁的访问数据库，对数据库的压力小很多
	 * Redis - 提供像 incr 和 increby 这样的自增原子命令，所以能保证生成的 ID 肯定是唯一有序的
	 *  - 优点: 不依赖于数据库，灵活方便，且性能优于数据库；数字 ID 天然排序，对分页或者需要排序的结果很有帮助
	 *  - 缺点: 需要 AOF 持久化；如果系统中没有 Redis，还需要引入新的组件，增加系统复杂度
	 * 雪花算法（SnowFlake） 
	 *  - 优点: 高性能，低延迟，按时间有序，一般不会造成 ID 碰撞
	 *  - 缺点: 强依赖于机器时钟，如果机器上时钟回拨，会导致发号重复或者服务会处于不可用状态
	 * 滴滴出品（TinyID）
	 *  - 基于号段模式原理实现，提供 http 和 tinyid-client 两种方式接入
	 * 百度 （Uidgenerator） 
	 *  - 基于 Snowflake 算法实现，支持自定义时间戳、工作机器 ID 和 序列号 等各部分的位数，采用用户自定义 workId 的生成策略
	 *    DefaultUidGenerator 就是正常的根据时间戳和机器位还有序列号的生成方式，和雪花算法很相似，对于时钟回拨也只是抛异常处理。仅有一些不同，如以秒为为单位而不再是毫秒
	 *    CachedUidGenerator 实现 使用 RingBuffer 缓存生成的 id。数组每个元素成为一个 slot。RingBuffer 容量，默认为 Snowflake 算法中 sequence 最大值（2^13 = 8192）。可通过 boostPower 配置进行扩容，以提高 RingBuffer 读写吞吐量
	 * 美团（Leaf） https://tech.meituan.com/2017/04/21/mt-leaf.html
	 *  - 同时支持号段模式和 snowflake 算法模式，可以切换使用
	 *  Leaf-segment 数据库方案
	 *    1. 利用 proxy server 批量获取，每次获取一个 segment(step 决定大小)号段的值。用完之后再去数据库获取新的号段，可以大大的减轻数据库的压力
	 *    2. 各个业务不同的发号需求用 biz_tag 字段来区分，每个 biz-tag 的 ID 获取相互隔离，互不影响。如果以后有性能需求需要对数据库扩容，不需要上述描述的复杂的扩容操作，只需要对 biz_tag 分库分表就行
	 *    优化思路：step 设置得足够大；双 buffer 的方式，Leaf 服务内部有两个号段缓存区 segment。当前号段已下发 10% 时，如果下一个号段未更新，则另启一个更新线程去更新下一个号段。当前号段全部下发完后，如果下个号段准备好了则切换到下个号段为当前 segment 接着下发，循环往复；
	 *  Leaf-snowflake 方案
	 *    1. 启动 Leaf-snowflake 服务，连接 Zookeeper，在 leaf_forever 父节点下检查自己是否已经注册过（是否有该顺序子节点）。
	 *    2. 如果有注册过直接取回自己的 workerID（zk 顺序节点生成的 int 类型 ID 号），启动服务。
	 *    3. 如果没有注册过，就在该父节点下面创建一个持久顺序节点，创建成功后取回顺序号当做自己的 workerID 号，启动服务
	 *    优化思路：在本机文件系统上缓存一个 workerID 文件，减少对 Zookeeper 的依赖性
	 *    错误告警：Leaf-snowflake 在解决时钟回拨的问题上是通过校验自身系统时间与 leaf_forever/${self} 节点记录时间做比较然后启动报警的措施。在时钟回拨的时候直接不提供服务直接返回 ERROR_CODE，等时钟追上即可。或者做一层重试，然后上报报警系统，又或者是发现有时钟回拨之后自动摘除本身节点并报警
	 */
	
	public static void main(String[] args) {
		String uuid = UUID.randomUUID().toString().replaceAll("-","");
		System.out.println(uuid);
	}

}
